import socket
import struct
import textwrap
# from flexx import flx
# 格式
TAB_1 = "\t - "
TAB_2 = "\t\t - "
TAB_3 = "\t\t\t - "
TAB_4 = "\t\t\t\t - "

DATA_TAB_1 = "\t "
DATA_TAB_2 = "\t\t "
DATA_TAB_3 = "\t\t\t "
DATA_TAB_4 = "\t\t\t\t\t "


# struct模块来解决bytes和其他二进制数据类型的转换。

# socket.SOCK_RAW
# 原始套接字，普通的套接字无法处理ICMP、IGMP等网络报文，而SOCK_RAW可以；其次，SOCK_RAW也可以处理特殊的IPv4报文；
# 此外，利用原始套接字，可以通过IP_HDRINCL套接字选项由用户构造IP头。



def main():
        #     这里面有三个非常重要的参数，family，type，proto；

        # family：即协议域，又称协议族，常用的有AF_INET、AF_INET6、AF_LOCAL(又称AF_UNIX)、AF_ROUTE等等。协议族决定了socket()的地址类型，在通信中必须采用对应的地址，AF_INET决定了要用ipv4地址（32位的）与端口号（16位的）的组合、AF_UNIX决定了要用一个绝对路径名作为地址。

        # type:指socket类型。常用的socket类型有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。

        # proto：是指定协议常用的协议有IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等，它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。

        # 注意：并不是上面的type和protocol可以随意组合的，如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时，会自动选择type类型对应的默认协议。

        # 通常我们使用下面几种：

        # family:

        #          socket.AF_INET：指定通过IPV4进行连接

        #          socket.AF_INET6：指定通过IPV6进行通信

        # type  ：
        #                     socket.SOCK_STREAM:通过TCP进行通信

        #          socket.SOCK_DGRAM: 通过udp进行通信
    conn = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.ntohs(3)) #htons()--"Host to Network Short"

    while True:
        raw_data, addr = conn.recvfrom(65536)
    #     #与recv()类似，但返回值是（data,address）。其中data是包含接收数据的字符串，address是发送数据的套接字地址。
        dest_mac, src_mac, eth_proto, data = ethernet_frame(raw_data)
    #     print('\n以太帧')
        print(TAB_1 + '目的地址:{}, 源地址: {}, 协议：{}'.format(dest_mac, src_mac, eth_proto))
    #     ans = TAB_1 + '目的地址:{}, 源地址: {}, 协议：{}'.format(dest_mac, src_mac, eth_proto)
    #     print(ans)
        # 8  ipv4
        if (eth_proto == 8):
            (version, header_length, ttl, proto, src, target, data) = ipv4_packet(data)
            print(TAB_1 + 'IPv4 包:')
            print(TAB_2 + '版本(Version): {}, 首部长度（Header Length） : {} , 生存时间(TTL) : {}'.format(version, header_length, ttl))
            print(TAB_2 + '协议(Protocol): {}, 源地址(Source) : {} , 目的地址(Target) : {}'.format(proto, src, target, data))
            # ICMP
            if proto == 1:
                icmp_type, code, checksum, data = icmp_packet(data)
                print(TAB_1 + 'ICMP 包:')
                print(TAB_2 + '类型(Type): {}, 代码(Code): {}, 校验和(Checksum): {},'.format(icmp_type, code, checksum))
                print(TAB_2 + 'Data:')
                print(format_multi_line(DATA_TAB_3, data))
            
            # TCP
            elif proto == 6:
                (src_port, dest_port ,sequence, acknowledgement,flag_urg,flag_ack,flag_psh,flag_rst,flag_syn,flag_fin, data) = tcp_segment(data)
                print(TAB_1 + 'TCP Segment:')
                print(TAB_2 + '源端口(Source Port): {}, 目的端口(Destination Port): {}'.format(src_port, dest_port))
                print(TAB_2 + '序号(Sequence): {}, 确认序号(Acknowledgement): {}'.format(sequence, acknowledgement))
                print(TAB_2 + 'Flags')
                print(TAB_3 + 'URG: {}, ACK: {}, PSH: {}, RST: {}, SYN: {}, FIN: {}'.format(flag_urg,flag_ack,flag_psh,flag_rst,flag_syn,flag_fin))
                print(TAB_2 + 'Flags')
                print(TAB_2 + 'Data:')
                print(format_multi_line(DATA_TAB_3, data))
            
            # UDP
            elif proto == 17:
                src_port, dest_port, length, data = udp_segment(data)
                print(TAB_1 + 'UDP Segment:')
                print(TAB_2 + '(源端口)Source Port: {}, (目的端口)Destination Port: {}, (长度)Length: {}'.format(src_port, dest_port, length, data))
            
            # other
            else :
                print(TAB_1 + 'Data:')
                print(format_multi_line(DATA_TAB_2, data))
# 解析以太帧
def ethernet_frame(data):
    dest_mac, src_mac, proto = struct.unpack('! 6s 6s H', data[:14])
    # 将前14位拆分成 6字节， 6字节， 2 字节
    # 首位为！，即为大端模式标准对齐方式(network)
    # 默认为@，即使用本机的字符顺序(大端or小端)
    # h 代表C struct中的short类型，占2位
    return get_mac_addr(dest_mac), get_mac_addr(src_mac), socket.htons(proto), data[14:]


# socket.htons(x)
# Convert 16-bit positive integers from host to network byte order. 
# On machines where the host byte order is the same as network byte order, this is a no-op; 
# otherwise, it performs a 2-byte swap operation.


# 返回可读性较高MAC地址  (ie AA:BB:CC:DD:EE:FF)
def get_mac_addr(bytes_addr):
    bytes_str = map('{:02x}'.format ,bytes_addr)
    return ':'.join(bytes_str).upper()

# 解析 IPv4 packe
def ipv4_packet(data):
    version_header_length = data[0]
    version = version_header_length >> 4
    header_length = (version_header_length&15) *4
    ttl,proto,src,target=struct.unpack('! 8x B B 2x 4s 4s',data[:20])
    return version, header_length, ttl, proto, ipv4(src), ipv4(target), data[header_length:]

# 返回可读性高的IPv4 地址
def ipv4(addr):
    return '.'.join(map(str, addr))

# 解析ICMP协议
def icmp_packet(data):
    icmp_type, code, checksum = struct.unpack('! B B H', data[:4])
    return icmp_type, code, checksum, data[4:]

# 解析 TCP 段
def tcp_segment(data):
    (src_port, dest_port ,sequence, acknowledgement, offset_reserved_flags) = struct.unpack('! H H L L H', data[:14])
    offset = (offset_reserved_flags >> 12 )*4
    flag_urg = (offset_reserved_flags & 32) >> 5
    flag_ack = (offset_reserved_flags & 16) >> 4
    flag_psh = (offset_reserved_flags & 8) >> 3
    flag_rst = (offset_reserved_flags & 4) >> 2
    flag_syn = (offset_reserved_flags & 2) >> 1
    flag_fin = offset_reserved_flags & 1
    return src_port, dest_port ,sequence, acknowledgement,flag_urg,flag_ack,flag_psh,flag_rst,flag_syn,flag_fin, data[offset:]

# 解析 UDP 协议
def udp_segment(data):
    src_port, dest_port, size = struct.unpack('! H H 2x H', data[:8])
    return src_port, dest_port, size, data[8:]

# 格式化打印数据
def format_multi_line(prefix, string, size = 80):
    size -= len(prefix)
    if isinstance(string, bytes):
        string = ''.join(r'\x{:02x}'.format(byte) for byte in string)
        if size%2:
            size-=1
    return '\n'.join([prefix+line for line in textwrap.wrap(string, size)])
main()